﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using OMXTreeViewNodes;
using System.IO;
using Terminal.Advanced_AEUC_settings_controls.Advanced_controls;
using Terminal.Advanced_AEUC_settings_controls.Scripting;
using System.Drawing;
using CmdSrv;
using System.Windows.Forms;
using System.Threading;

namespace ScriptingExtension
{
    public delegate void PrintF(string text);

    class OMXCtrlShell : IOMXTreeViewParentForm
    {
        /// <summary>
        /// Communication module reference.
        /// </summary>
        CommLink.CommLink commLink;
        CommLink.CommLink.ConnectionStatusEvent cstHandler = null;

        public event PrintF OnPrintF;

        OMXParamTreeViewRootNode tNodeBase = null;
        /// <summary>
        /// Reference to the scripting module instance
        /// </summary>
        ScriptingModule scrFastScriptDiag = null;
        /// <summary>
        /// Indicates if the system has finished loading components
        /// </summary>
        AutoResetEvent readyToGo = new AutoResetEvent(false);

        #region OMX Component deserialization

        #region OMX Param root node loading

        public void deserializeOMXParamTreeViewFile(FileInfo finf)
        {
            tNodeBase = null;
            BinaryReader fsr = new BinaryReader(finf.OpenRead());
            try
            {
                int rootVersion;
                try
                {
                    rootVersion = fsr.ReadInt32();
                    if (rootVersion < OMXParamTreeViewRootNode.OMXCompRootNodeVerMin || rootVersion > OMXParamTreeViewRootNode.OMXCompRootNodeVerMax)
                        throw new Exception("Invalid version number - assuming pre-version 0 node.");
                }
                catch (Exception ex)
                {
                    fsr.BaseStream.Position -= 4;
                    rootVersion = 0;
                    commLink.errorMessage(ex.Message);
                }

                switch (rootVersion)
                {
                    case 0:
                        tNodeBase = loadOMXParamRootTreeView_Ver_x0(fsr, finf.Directory.ToString());
                        break;
                    case 1:
                        tNodeBase = loadOMXParamRootTreeView_Ver_x1(fsr, finf.Directory.ToString());
                        break;
                    case 2:
                        tNodeBase = loadOMXParamRootTreeView_Ver_x2(fsr, finf.Directory.ToString());
                        break;
                    case 3:
                        tNodeBase = loadOMXParamRootTreeView_Ver_x3(fsr, finf.Directory.ToString());
                        break;
                }
                tNodeBase.infParamTreeViewFile = finf;
                tNodeBase.UCTabPage.AttachCommLinkChannel(commLink);
                tNodeBase.UCTabPage.OnParamsStreamParse += new UCTabPage.paramsStreamParse(ucTabPage_OnparamsStreamParse);
                tNodeBase.UCTabPage.OnUCInternControlsUpdated += new UCTabPage.UCInternControlsUpdated(ucTabPage_OnUCInternControlsUpdated);

                int sectCount = fsr.ReadInt32();
                for (int i = 0; i < sectCount; i++)
                {
                    tNodeBase.Nodes.Add(loadOMXParamTreeView(fsr, tNodeBase.UCTabPage));
                }
            }
            catch (Exception ex)
            {
                tNodeBase = null;
                commLink.errorMessage(ex.Message);
            }
            finally
            {
                fsr.Close();
                finf.Refresh();
            }

            tNodeBase.UCTabPage.LoadUserSettingDefinitionsFileNoProgBar(tNodeBase.infUCTabPageControlsFile);

            readyToGo.WaitOne();
        }

        #region root node version loading
        OMXParamTreeViewRootNode loadOMXParamRootTreeView_Ver_x0(BinaryReader fsr, string dirPath)
        {
            return new OMXParamTreeViewRootNode(fsr.ReadString(), /*ucTabPage,*/ this, dirPath + "\\" + fsr.ReadString());
        }
        OMXParamTreeViewRootNode loadOMXParamRootTreeView_Ver_x1(BinaryReader fsr, string dirPath)
        {
            string name = fsr.ReadString();
            string OMXname = fsr.ReadString();
            OMXParamTreeViewRootNode tNodeBase = new OMXParamTreeViewRootNode(name, /*ucTabPage,*/ this, dirPath + "\\" + fsr.ReadString());
            tNodeBase.OMXCopmName = OMXname;
            return tNodeBase;
        }
        OMXParamTreeViewRootNode loadOMXParamRootTreeView_Ver_x2(BinaryReader fsr, string dirPath)
        {
            string name = fsr.ReadString();
            string OMXname = fsr.ReadString();
            string cfgDCC = fsr.ReadString();
            OMXParamTreeViewRootNode tNodeBase = new OMXParamTreeViewRootNode(name, /*ucTabPage,*/ this, dirPath + "\\" + fsr.ReadString());
            tNodeBase.OMXCopmName = OMXname;
            tNodeBase.cfgDCC = cfgDCC;
            return tNodeBase;
        }
        OMXParamTreeViewRootNode loadOMXParamRootTreeView_Ver_x3(BinaryReader fsr, string dirPath)
        {
            string name = fsr.ReadString();
            string OMXname = fsr.ReadString();
            string cfgDCC = fsr.ReadString();
            string uriDCC = fsr.ReadString();
            string bufDCC = fsr.ReadString();
            OMXParamTreeViewRootNode tNodeBase = new OMXParamTreeViewRootNode(name, /*ucTabPage,*/ this, dirPath + "\\" + fsr.ReadString());
            tNodeBase.OMXCopmName = OMXname;
            tNodeBase.cfgDCC = cfgDCC;
            tNodeBase.uriDCC = uriDCC;
            tNodeBase.bufDCC = bufDCC;
            return tNodeBase;
        }
        #endregion

        #endregion

        #region OMX Param section leafs loading

        private TreeNode loadOMXParamTreeView(BinaryReader fsr, UCTabPage refUC)
        {
            int sectVersion;
            try
            {
                sectVersion = fsr.ReadInt32();
                if (sectVersion < OMXParamTreeViewSectionNode.OMXCompSectNodeVerMin || sectVersion > OMXParamTreeViewSectionNode.OMXCompSectNodeVerMax)
                    throw new Exception("Invalid version number - assuming pre-version 0 node.");
            }
            catch(Exception ex)
            {
                fsr.BaseStream.Position -= 4;
                sectVersion = 0;
                commLink.errorMessage(ex.Message);
            }

            switch (sectVersion)
            {
                case 0:
                    return loadOMXParamTreeView_Ver_x0(fsr, refUC);
                default:
                    throw new Exception("Unsupported node version encountered.");
            }
        }

        #region Section node version loading
        TreeNode loadOMXParamTreeView_Ver_x0(BinaryReader fsr, UCTabPage refUC)
        {
            string name = fsr.ReadString();
            Int32 filesCount = fsr.ReadInt32();
            string[] filePaths = new string[filesCount];
            int[] refPages = new int[filesCount];
            for (int i = 0; i < filesCount; i++)
            {
                filePaths[i] = fsr.ReadString();
                refPages[i] = fsr.ReadInt32();
            }
            TreeNode tNode = new OMXParamTreeViewSectionNode(name, filePaths, refPages, refUC, this);
            Int32 childCount = fsr.ReadInt32();
            while (childCount-- > 0)
            {
                tNode.Nodes.Add(loadOMXParamTreeView(fsr, refUC));
            }

            return tNode;
        }
        #endregion

        #endregion

        #region AUEC registered callbacks

        void ucTabPage_OnparamsStreamParse(UCTabPage.ParameterStreamParseResult state, int compId, int tabId)
        {
            switch (state)
            {
                case UCTabPage.ParameterStreamParseResult.PARSE_OK:
                    break;
                case UCTabPage.ParameterStreamParseResult.PARSE_FAIL:
                    break;
                case UCTabPage.ParameterStreamParseResult.PARSE_UNRECOGNIZED:
                    break;
                case UCTabPage.ParameterStreamParseResult.CONFIG_APPLY_ACK:
                    break;
                case UCTabPage.ParameterStreamParseResult.OMX_COMP_HANDLE:
                    break;
                case UCTabPage.ParameterStreamParseResult.OMX_COMP_CMD:
                    break;
                case UCTabPage.ParameterStreamParseResult.OMX_COMP_PORT_BUFF_ALLOC:
                    break;
            }
        }

        void ucTabPage_OnUCInternControlsUpdated()
        {
            readyToGo.Set();
        }

        #endregion

        #region Dummy interface functions

        public void OMXParamTreeViewNodeSelected(TreeNode Node)
        {
            throw new NotImplementedException();
        }

        public void saveOMXParamTreeView(OMXParamTreeViewSectionNode sect, BinaryWriter fBin)
        {
            throw new NotImplementedException();
        }

        public void saveOMXParamTreeView(OMXParamTreeViewRootNode root, FileInfo fInf)
        {
            throw new NotImplementedException();
        }

        #endregion

        #endregion

        /// <summary>
        /// Creates an OMX Component and structure array from available components
        /// </summary>
        /// <returns>Array of available OMX Components to be used by scripting module</returns>
        public ScriptingModule.OMXComponent[] getAvailableOMXComp()
        {
            ScriptingModule.OMXComponent[] compTbl = new ScriptingModule.OMXComponent[1];

            compTbl[0] = new ScriptingModule.OMXComponent();
            compTbl[0].omxName = tNodeBase.Text;
            compTbl[0].messages = tNodeBase.UCTabPage.getCMsgDefs();
            compTbl[0].gtpProperties = tNodeBase.UCTabPage.getTabPageProps();

            return compTbl;
        }

        #region Communication handling

        /// <summary>
        /// Returns a reference to the active communication object
        /// </summary>
        /// <returns>Communication link object reference</returns>
        public CommLink.CommLink GetCommLinkObject()
        {
            return commLink;
        }
        /// <summary>
        /// Creates a communication link object based on user media selection.
        /// </summary>
        public void CreateCommLinkChannel(bool defaultConnection)
        {
            if (commLink != null)
            {
                commLink.CommClose();
                commLink = null;
            }

            object enumSelection = null;
            if (defaultConnection)
                enumSelection = CommLink.CommLink.GetDefaultConnectionType();

            if (enumSelection == null)
            {
                MessageBox.Show("Communication channel not established!");
                enumSelection = CommLink.CommLink.CommLinkType.No_Communication;
            }

            commLink = new CommLink.CommLink((CommLink.CommLink.CommLinkType)enumSelection);
            commLink.OnConnectionStatusEvent += new CommLink.CommLink.ConnectionStatusEvent(commLink_OnConnStateChange);
            if(cstHandler != null)
                commLink.OnConnectionStatusEvent += cstHandler;
            commLink.establishConnection();
        }
        /// <summary>
        /// Closes the communication channel if its active
        /// </summary>
        public void CloseCommLinkChannel()
        {
            if (commLink != null)
            {
                commLink.CommClose();
                commLink = null;
            }
        }
        /// <summary>
        /// Communication change events
        /// </summary>
        /// <param name="eState">Reported communication state</param>
        void commLink_OnConnStateChange(CommLink.CommLinkAbstract.ConnectionStates eState)
        {
            switch (eState)
            {
                case CommLink.CommLinkAbstract.ConnectionStates.Connected:
                    tNodeBase.UCTabPage.AttachCommLinkChannel(commLink);
                    break;
                case CommLink.CommLinkAbstract.ConnectionStates.NotConnected:
                    break;
                case CommLink.CommLinkAbstract.ConnectionStates.ServerError:
                    break;
            }
        }
        
        public void commLinkAttachStateEventHandler(CommLink.CommLink.ConnectionStatusEvent stateHandle)
        {
            cstHandler  = new CommLink.CommLink.ConnectionStatusEvent(stateHandle);
        }
        #endregion

        #region Script command handling
        public void CreateScriptingModule()
        {
            scrFastScriptDiag = new ScriptingModule(new ScriptingModule.GetAvailableOMXComponentsDelegate(getAvailableOMXComp),
                                        new ScriptingModule.GetActiveCommLinkDelegate(GetCommLinkObject), true,
                                        new ScriptingModule.CreateDefaultComLinkDelegate(CreateCommLinkChannel), null);
            scrFastScriptDiag.NoGUI = true;

            scrFastScriptDiag.RegisterPrintFunc(new Terminal.Advanced_AEUC_settings_controls.Scripting.ScriptPrint(PrintCommandFunc));
        }

        public void DestroyScriptModule()
        {
            if (scrFastScriptDiag != null)
            {
                scrFastScriptDiag.Close();
                scrFastScriptDiag = null;
            }
        }

        public void SetScriptModuleRunDir(string dir)
        {
            ScriptModule.Properties.Settings.Default.FastScriptMenuFolder = dir;
            ScriptModule.Properties.Settings.Default.Save();
        }

        public string GetScriptModuleRunDir()
        {
            return ScriptModule.Properties.Settings.Default.FastScriptMenuFolder;
        }

        public uint ExecuteScriptFile(string sFile, out string sMessage)
        {
            if (scrFastScriptDiag != null)
            {
                ScriptingModule.ScrErrorReport scrResult = scrFastScriptDiag.executeScriptFile(sFile);
                sMessage = scrResult.Message;
                return scrResult.Code;
            }
            else
            {
                sMessage = "Script module not initialized! Connect to target first";
                return 0xF000F000;
            }
        }

        public uint ExecuteScriptCmd(string sCmd, out string sMessage)
        {
            if (scrFastScriptDiag != null)
            {
                ScriptingModule.ScrErrorReport scrResult = scrFastScriptDiag.executeScript(sCmd);
                sMessage = scrResult.Message;
                return scrResult.Code;
            }
            else
            {
                sMessage = "Script module not initialized! Connect to target first";
                return 0xF000F000;
            }
        }

        public void PrintCommandFunc(string text)
        {
            if (OnPrintF != null)
                OnPrintF(text);
        }

        public void StepScriptExecution()
        {
            scrFastScriptDiag.executeStepScript();
        }

        public void FreeRunScriptExecution()
        {
            scrFastScriptDiag.executeRunScript();
        }

        public void TerminateScriptExecution()
        {
            scrFastScriptDiag.terminateExecution();
        }

        public void SetScriptExecutionLine(int execLn)
        {
            scrFastScriptDiag.setExecLineTo(execLn);
        }

        public string ObtainCurrentScriptSnippet(int snipLen)
        {
            int startLine = 0;
            int endLine = 0;
            int crntLine = scrFastScriptDiag.getExecLineNum();
            
            if (crntLine < 0)
            {
                return "Not executing any script";
            }

            string[] cmdLines = scrFastScriptDiag.getScriptLine();

            string snippet = "";

            startLine = crntLine - snipLen / 2;
            if (startLine < 0)
                startLine = 0;
            endLine = startLine + snipLen + 1;
            if (endLine > cmdLines.Length)
                endLine = cmdLines.Length;

            for (int i = startLine; i < endLine; i++)
            {
                if (i == crntLine)
                    snippet += "-->";
                snippet += cmdLines[i] + "\n";
            }
            return snippet;
        }

        public byte[] GetStructBytestream(string structName)
        {
            return scrFastScriptDiag.getStruct("RTE", structName);
        }

        public byte[] GetStructFieldBytestream(string structName, string fieldName)
        {
            return scrFastScriptDiag.getStructField("RTE", structName, fieldName);
        }

        #endregion
    }

    /// <summary>
    /// Provides access and contro to OMX scripting module
    /// </summary>
    public class OMXScriptCtrl
    {
        OMXCtrlShell omxShell;
        /// <summary>
        /// Status events enumeration
        /// </summary>
        public enum StatusUpdates
        {
            Done,
            Error,
            ServerConnected,
            ServerDisconnected,
            ServerError
        }
        /// <summary>
        /// Status update event delegate
        /// </summary>
        /// <param name="stat">Status event</param>
        /// <param name="custSt">Status code</param>
        public delegate void StatusUpdate(StatusUpdates stat, Int32 custSt);
        /// <summary>
        /// Provides connection status and other event updates
        /// </summary>
        public event StatusUpdate OnStatusUpdate;
        /// <summary>
        /// Public constructor
        /// </summary>
        public OMXScriptCtrl()
        {
            omxShell = new OMXCtrlShell();
            omxShell.commLinkAttachStateEventHandler(commLink_OnConnStateChange);
        }

        void commLink_OnConnStateChange(CommLink.CommLinkAbstract.ConnectionStates eState)
        {
            if (OnStatusUpdate != null)
            {
                switch (eState)
                {
                    case CommLink.CommLinkAbstract.ConnectionStates.Connected:
                        OnStatusUpdate(StatusUpdates.ServerConnected, 0);
                        break;
                    case CommLink.CommLinkAbstract.ConnectionStates.NotConnected:
                        OnStatusUpdate(StatusUpdates.ServerDisconnected, 0);
                        break;
                    case CommLink.CommLinkAbstract.ConnectionStates.ServerError:
                        OnStatusUpdate(StatusUpdates.ServerError, -1);
                        break;
                }
            }
        }

        /// <summary>
        /// Coonfigure the scripting module script directory for relative path calls
        /// </summary>
        /// <param name="sDir">Script directory path</param>
        public void SetScriptsDirectory(string sDir)
        {
            omxShell.SetScriptModuleRunDir(sDir);
        }
        /// <summary>
        /// Get the current script module directory
        /// </summary>
        /// <returns>Script directory path</returns>
        public string GetScriptsDirectory()
        {
            return omxShell.GetScriptModuleRunDir();
        }
        /// <summary>
        /// Load specified OMX Component definition file
        /// </summary>
        /// <param name="fileName">Path to OMX Component file</param>
        public void LoadOMXComponent(string fileName)
        {
            omxShell.deserializeOMXParamTreeViewFile(new FileInfo(fileName));
        }
        /// <summary>
        /// Open TCP/IP connection to mms_il_module
        /// </summary>
        /// <param name="tcpip">TCP/IP address</param>
        /// <param name="port">Server port</param>
        public void OpenTCPComm(string tcpip, int port)
        {
            CommLink.Properties.Settings.Default.Default_communication_type = "TCP_Channel";
            CommLink.Properties.Settings.Default.TCP_HostAddr = tcpip;
            CommLink.Properties.Settings.Default.TCP_HostPort = port;
            CommLink.Properties.Settings.Default.Save();

            omxShell.CreateCommLinkChannel(true);
        }
        /// <summary>
        /// Close the active mms_il_module connection
        /// </summary>
        public void CloseTCPComm()
        {
            omxShell.CloseCommLinkChannel();
        }
        /// <summary>
        /// Get the last set mms_il_module TCP/IP address and port
        /// </summary>
        /// <param name="tcpip">TCP/IP address</param>
        /// <param name="port">Server port</param>
        public void GetTCPComm(out string tcpip, out int port)
        {
            tcpip = CommLink.Properties.Settings.Default.TCP_HostAddr;
            port = CommLink.Properties.Settings.Default.TCP_HostPort;
        }
        /// <summary>
        /// Startup scripting module - requires active mms_il_module connection to succeed
        /// </summary>
        public void StartScriptingModule()
        {
            omxShell.CreateScriptingModule();
        }
        /// <summary>
        /// Register a callback function for script print commands
        /// </summary>
        /// <param name="printfunc">Print function callback</param>
        public void RegisterPrintFunction(PrintF printfunc)
        {
            omxShell.OnPrintF += printfunc;
        }
        /// <summary>
        /// Stop and clean up of active scripting module instance
        /// </summary>
        public void StopScriptingModule()
        {
            omxShell.DestroyScriptModule();
        }
        /// <summary>
        /// Execute a script file
        /// </summary>
        /// <param name="sFile">Path to file</param>
        /// <param name="sMessage">Out error message</param>
        /// <returns>Error code</returns>
        public uint ExecuteScriptFile(string sFile, out string sMessage)
        {
            return omxShell.ExecuteScriptFile(sFile, out sMessage);
        }
        /// <summary>
        /// Execute a script command
        /// </summary>
        /// <param name="sFile">Path to file</param>
        /// <param name="sMessage">Out error message</param>
        /// <returns>Error code</returns>
        public uint ExecuteScriptCommand(string sCmd, out string sMessage)
        {
            return omxShell.ExecuteScriptCmd(sCmd, out sMessage);
        }
        /// <summary>
        /// Terminates the current script execution if any
        /// </summary>
        public void TerminateScriptExecution()
        {
            omxShell.TerminateScriptExecution();
        }
        /// <summary>
        /// Set the current script line to be executed
        /// </summary>
        /// <param name="lineNum">Line number</param>
        public void SetExecutionLineNum(int lineNum)
        {
            omxShell.SetScriptExecutionLine(lineNum);
        }
        /// <summary>
        /// Set script step by step mode
        /// </summary>
        public void StepScript()
        {
            omxShell.StepScriptExecution();
        }
        /// <summary>
        /// Set script free run mode
        /// </summary>
        public void ResumeScript()
        {
            omxShell.FreeRunScriptExecution();
        }
        /// <summary>
        /// Obtain a script snippet of the active script
        /// </summary>
        /// <param name="SnipLen">Lenght in lines of the snippet</param>
        /// <returns>String containing the active script snippet</returns>
        public string ScriptSnippet(int SnipLen)
        {
            return omxShell.ObtainCurrentScriptSnippet(SnipLen);
        }
        /// <summary>
        /// Returns the bytestream of a selected structure
        /// </summary>
        /// <param name="structName">Structure name</param>
        /// <returns>Structure bytestream</returns>
        public byte[] GetStructureBytestream(string structName)
        {
            return omxShell.GetStructBytestream(structName);
        }
        /// <summary>
        /// Returns the bytestream of a selected structure's field
        /// </summary>
        /// <param name="structName">Structure name</param>
        /// <param name="fieldName">Structure field name</param>
        /// <returns>Field bytestream</returns>
        public byte[] GetStructFieldBytestream(string structName, string fieldName)
        {
            return omxShell.GetStructFieldBytestream(structName, fieldName);
        }
    }
}
